﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
using System.Windows.Forms;
using JetBrains.Annotations;
using MultiLang;
using Rsdn.Janus.Core.Synchronization;
using Rsdn.Janus.Log;
using Rsdn.SmartApp;

namespace Rsdn.Janus
{
    /// <summary>
    /// Реализует синхронизацию с веб-сервисом.
    /// </summary>
    [Service(typeof(ISynchronizer))]
    internal class Synchronizer : ISynchronizer, IDisposable
    {
        private readonly IServiceProvider _provider;
        private readonly AsyncOperation _uiAsyncOp;
        private readonly Subject<EventArgs> _startSync = new Subject<EventArgs>();
        private readonly IObservable<EventArgs> _startSyncAsync;
        private readonly Subject<EndSyncEventArgs> _endSync = new Subject<EndSyncEventArgs>();
        private readonly IObservable<EndSyncEventArgs> _endSyncAsync;
        private SyncForm _lastSyncFormInstance;
        private bool _isActive;
        private readonly ExtensionsCache<SyncProviderInfo, string, ISyncProvider> _syncProvidersCache;
        private readonly CompositeDisposable _disposables;

        public Synchronizer([NotNull] IServiceProvider provider)
        {
            if (provider == null)
                throw new ArgumentNullException("provider");                      //MLHIDE

            _provider = provider;

            _syncProvidersCache = new ExtensionsCache<SyncProviderInfo, string, ISyncProvider>(provider);

            SynchronizationContext syncContext = _provider.GetRequiredService<IUIShell>().UISyncContext;
            _startSyncAsync = _startSync.ObserveOn(syncContext);
            _endSyncAsync = _endSync.ObserveOn(syncContext);
            _uiAsyncOp = _provider.GetRequiredService<IUIShell>().CreateUIAsyncOperation();

            _disposables =
                new CompositeDisposable(
                    _startSync.CompleteHelper(),
                    _endSync.CompleteHelper(),
                    Disposable.Create(_uiAsyncOp.OperationCompleted));
        }

        #region ISynchronizer Members

        public bool IsActive()
        {
            return _isActive;
        }

        public bool IsAvailable()
        {
            ISyncProvider[] syncProviders = _syncProvidersCache.GetAllExtensions();

            return syncProviders.All(syncProv => syncProv.IsAvailable());
        }

        /// <summary>
        /// Start synchronization
        /// All subscribers should be already setup for events
        /// It uses Logger to notify about progress
        /// </summary>
        public IStatisticsContainer SyncPeriodic(bool activateUI)
        {
            SyncProviderInfo[] syncProviderInfos = _syncProvidersCache.GetExtensionInfos();

            Action<ISyncContext> action =
                context => syncProviderInfos.ForEach(info => PerformSyncProvider(context, info.Key, null));

            return PerformSyncSession(action, activateUI);
        }

        /// <summary>
        /// Выполнить указанную задачу синхронизации.
        /// </summary>
        /// <param name="providerName"></param>
        /// <param name="taskName"></param>
        /// <param name="activateUI"></param>
        /// <exception cref="ArgumentException"></exception>
        /// <returns></returns>
        public IStatisticsContainer SyncSpecific(string providerName, string taskName, bool activateUI)
        {
            if (providerName == null)
            {
                throw new ArgumentNullException("providerName"); //MLHIDE
            }
            if (taskName == null)
            {
                throw new ArgumentNullException("taskName"); //MLHIDE
            }
            ISyncProvider prov = _syncProvidersCache.GetExtension(providerName);
            if (prov == null)
            {
                throw new ArgumentException(ml.ml_string(382, "Неизвестный провайдер синхронизации"));
            }

            return
                PerformSyncSession(
                    context =>
                        PerformSyncProvider(
                            context,
                            providerName,
                            taskName),
                    activateUI);
        }

        public IObservable<EventArgs> StartSync
        {
            get { return _startSyncAsync; }
        }

        public IObservable<EndSyncEventArgs> EndSync
        {
            get { return _endSyncAsync; }
        }

        #endregion

        #region Implementation of IDisposable

        public void Dispose()
        {
            _disposables.Dispose();
        }

        #endregion

        #region Private Members

        /// <summary>
        /// Инициализация переменных и событий для синхронизации. 
        /// </summary>
        private bool InitStartSync()
        {
            if (IsActive())
            {
                _provider.LogWarning(SyncResources.SyncAlreadyActive);
                return false;
            }

            _isActive = true;
            OnStartSync();

            return true;
        }

        private void PerformSyncProvider(
            ISyncContext context,
            string provName,
            string taskName)
        {
            ISyncProvider syncProv = _syncProvidersCache.GetExtension(provName);
            if (syncProv == null)
            {
                throw new ArgumentException(ml.ml_string(383, "Неизвестный провайдер"));
            }

            if (taskName == null)
            {
                syncProv.SyncPeriodicTasks(context);
            }
            else
            {
                syncProv.SyncTask(context, taskName);
            }
        }

        private IStatisticsContainer PerformSyncSession(
            Action<ISyncContext> syncProc,
            bool activateUI)
        {
            // Note AN  Create SyncForm here

            StatisticsContainer stats = new StatisticsContainer();

            if (!InitStartSync())
            {
                return stats;
            }

            ServiceManager svcManager = new ServiceManager(_provider);
            SyncForm syncForm = null;
            SyncResult result = SyncResult.Failed;
            Exception failException = null;
            try
            {
                SyncContext context = new SyncContext(
                    svcManager,
                    stats,
                    // ReSharper disable AccessToModifiedClosure
                    () => syncForm != null && syncForm.IsCancelled);
                    // ReSharper restore AccessToModifiedClosure	

                if (_lastSyncFormInstance != null)
                {
                    _uiAsyncOp.Send(_lastSyncFormInstance.Dispose);
                }
                if (Config.Instance.ShowSyncWindow)
                {
                    _uiAsyncOp.Send(
                        () =>
                        {
                            syncForm = new SyncForm(context);
                            syncForm.WindowState = activateUI
                                                       ? FormWindowState.Normal
                                                       : FormWindowState.Minimized;
                            syncForm.Show();
                        });
                    syncForm.Closed += (sender, args) => _lastSyncFormInstance = null;

                    _lastSyncFormInstance = syncForm;
                    svcManager.Publish<ISyncProgressVisualizer>(syncForm);
                    svcManager.Publish<ITaskIndicatorProvider>(syncForm);
                    svcManager.Publish<ISyncErrorInformer>(syncForm);
                }
                Application.DoEvents();
                syncProc(context);

                if (!stats.IsEmpty())
                {
                    _provider.LogInfo(stats.GetFormattedValues(_provider));
                }

                result = SyncResult.Finished;
            }
            catch (UserCancelledException ex)
            {
                _provider.LogWarning(SyncResources.SyncWarning.FormatStr(ex.Message));
                result = SyncResult.Aborted;
            }
            catch (Exception ex)
            {
                _provider.LogError(SyncResources.SyncError.FormatStr(ex.Message));
                result = SyncResult.Failed;
                failException = ex;
            }
            finally
            {
                _isActive = false;
                OnEndSync(new EndSyncEventArgs(stats, result, failException));
                if (syncForm != null)
                {
                    _uiAsyncOp.Send(syncForm.TryClose);
                }
            }
            return stats;
        }

        private void OnStartSync()
        {
            _startSync.OnNext(EventArgs.Empty);
        }

        private void OnEndSync(EndSyncEventArgs arg)
        {
            _endSync.OnNext(arg);
        }
        #endregion
    }
}